package cn.iocoder.yudao.module.infra.framework.log;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.infra.framework.log.config.HttpTraceConfig;
import cn.iocoder.yudao.module.infra.util.TraceIdUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;

import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;

@Slf4j
@RequiredArgsConstructor
public class HttpTraceInterceptor implements HandlerInterceptor {
    /**
     * HTTP 请求时，访问令牌的请求 Header
     */
    private String tokenHeader = "Authorization";
    /**
     * HTTP 请求时，访问令牌的请求参数
     * <p>
     * 初始目的：解决 WebSocket 无法通过 header 传参，只能通过 token 参数拼接
     */
    private String tokenParameter = "token";

    // 分配的应用appId
    String X_DD_APP_KEY = "X-DD-AppKey";

    // 请求唯一标识符，相同的值只会处理一次请求，长度在32个字符以内
    String X_DD_REQUEST_ID = "X-DD-RequestId";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        try {
            // 从请求头中获取TID
            final String appKey = request.getHeader(X_DD_APP_KEY);
            final String requestId = request.getHeader(X_DD_REQUEST_ID);

            final String tid = StrUtil.isNotEmpty(requestId) ? requestId : IdUtil.fastSimpleUUID();

            // 将TID存储到MDC中，使其在整个请求过程中可用
            TraceIdUtil.setTraceId(request, tid);

            // 设置请求开始执行时间
            request.setAttribute("startTime", System.currentTimeMillis());

            final Long userId = WebFrameworkUtils.getLoginUserId(request);
            final Map<String, String> queryString = ServletUtils.getParamMap(request);
            final String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtils.getBody(request) : null;

            final String token = SecurityFrameworkUtils.obtainAuthorization(request, tokenHeader, tokenParameter);

            if (ObjUtil.isNotEmpty(appKey) && ObjUtil.isNotEmpty(requestId)) {
                log.info("Http请求IN-开始, method = {}, uri = {}, appKey = {}, requestId = {}, queryString = {}, requestBody = {}",
                        request.getMethod(), request.getRequestURI(), appKey, requestId, queryString, requestBody);
            } else {
                log.info("Http请求IN-开始, method = {}, uri = {}, token = {}, userId = {}, queryString = {}, requestBody = {}",
                        request.getMethod(), request.getRequestURI(), token, userId, queryString, requestBody);
            }
        } catch (Exception e) {
            log.error("Http请求IN日志-异常", e);
        }

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                                Exception ex) throws Exception {
        try {
            final CommonResult<?> result = ServletUtils.getCommonResult(request);

            final long startTime = Long.parseLong(request.getAttribute("startTime").toString());
            final LocalDateTime endTime = LocalDateTimeUtil.now();
            final long duration = LocalDateTimeUtil.between(LocalDateTimeUtil.of(startTime), endTime,
                    ChronoUnit.MILLIS);
            final int statusCode = response.getStatus();

            printLog(request, ex, result, duration, statusCode);
        } catch (Exception e) {
            log.error("Http请求IN日志-异常", e);
        } finally {
            // 清除MDC中的TID，以免影响其他请求
            TraceIdUtil.removeTraceId();
        }
    }

    private void printLog(HttpServletRequest request, Exception ex, CommonResult<?> result,
                          long duration, int statusCode) {
        final String uri = request.getRequestURI();
        if (result != null) {
            final boolean disableResponseBody = isDisableResponseBody(request);
            if (disableResponseBody) {
                if (result.isSuccess()) {
                    log.info("Http请求IN-成功, uri = {}, duration = {}Ms, statusCode = {}", uri, duration, statusCode);
                } else {
                    log.warn("Http请求IN-失败, uri = {}, duration = {}Ms, statusCode = {}, resultCode = {}, resultMsg = {}, resultData = {}",
                            uri, duration, statusCode, result.getCode(), result.getMsg(), toJsonString(result.getData()));
                }
            } else {
                if (result.isSuccess()) {
                    log.info("Http请求IN-成功, uri = {}, duration = {}Ms, statusCode = {}, resultData = {}",
                            uri, duration, statusCode, toJsonString(result.getData()));
                } else {
                    log.warn("Http请求IN-失败, uri = {}, duration = {}Ms, statusCode = {}, resultCode = {}, resultMsg = {}, resultData = {}",
                            uri, duration, statusCode, result.getCode(), result.getMsg(), toJsonString(result.getData()));
                }
            }
        } else {
            if (ex != null) {
                log.warn(String.format("Http请求IN-异常, uri = %s, duration = %sMs, statusCode = %s",
                        uri, duration, statusCode), ex);
            } else {
                if (statusCode == 200) {
                    log.info("Http请求IN-成功, uri = {}, duration = {}Ms, statusCode = {}", uri, duration, statusCode);
                } else {
                    log.warn("Http请求IN-失败, uri = {}, duration = {}Ms, statusCode = {}", uri, duration, statusCode);
                }
            }
        }
    }

    private boolean isDisableResponseBody(HttpServletRequest request) {
        final String requestURI = request.getRequestURI();
        final HttpTraceConfig httpTraceConfig = HttpTraceConfig.getHttpTraceConfig();

        final List<String> disableResponseBodyUrls = httpTraceConfig.getDisableResponseBodyUrls();
        if (CollUtil.isNotEmpty(disableResponseBodyUrls)) {
            for (String urlPatter : disableResponseBodyUrls) {
                final boolean match = ReUtil.isMatch(urlPatter, requestURI);
                if (match) {
                    return true;
                }
            }
        }
        return false;
    }


}
